package cn.monkey.data.extension.crud;

import cn.monkey.data.entity.EntityMapper;
import cn.monkey.data.entity.dto.QueryRequest;
import cn.monkey.data.entity.utils.ClassUtil;
import cn.monkey.data.entity.vo.Result;
import cn.monkey.data.entity.vo.Results;
import cn.monkey.data.entity.dto.ExtensionQueryRequest;
import cn.monkey.data.repository.QueryRequestRepository;
import org.mapstruct.factory.Mappers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public abstract class AbstractCrudService<Q extends QueryRequest, T, ID extends Serializable,
        D, R, Repository extends QueryRequestRepository<Q, T, ID>, Mapper extends EntityMapper<D, T, R>>
        implements CrudService<ID, Q, D, R> {
    protected final Repository requestRepository;

    protected final Mapper entityMapper;

    public AbstractCrudService(Repository requestRepository) {
        this(requestRepository, null);
    }

    public AbstractCrudService(Repository requestRepository, Mapper mapper) {
        this.requestRepository = requestRepository;
        if (mapper == null) {
            Class<Mapper> mapperC = ClassUtil.getActualType(this, AbstractCrudService.class, "Mapper");
            this.entityMapper = Mappers.getMapper(mapperC);
        } else {
            this.entityMapper = mapper;
        }
    }

    @Override
    public Result<Page<R>> read(ExtensionQueryRequest request, Q queryRequest, Pageable pageable) {
        Page<T> page = this.requestRepository.selectPageByQueryRequest(queryRequest, pageable);
        return Results.ok(page.map(this.entityMapper::copyToVo));
    }

    @Override
    public Result<R> read(ExtensionQueryRequest request, ID id) {
        return this.requestRepository.findById(id)
                .map(this.entityMapper::copyToVo)
                .map(Results::ok)
                .orElse(Results.fail("can not find by id:" + id));
    }

    @Override
    public Result<Collection<R>> read(ExtensionQueryRequest extensionQueryRequest, Collection<ID> ids) {
        List<R> list = this.requestRepository.findAllById(ids)
                .stream()
                .map(this.entityMapper::copyToVo)
                .toList();
        return Results.ok(list);
    }

    @Override
    public Result<R> readOne(ExtensionQueryRequest request, Q queryRequest) {
        return this.requestRepository.selectOneByQueryRequest(queryRequest)
                .map(this.entityMapper::copyToVo)
                .map(Results::ok)
                .orElse(Results.fail("can not find by queryRequest"));
    }

    @Override
    public Result<Collection<R>> read(ExtensionQueryRequest request, Q queryRequest, Sort sort) {
        List<R> list = this.requestRepository.selectByQueryRequest(queryRequest, sort)
                .stream().map(this.entityMapper::copyToVo).toList();
        return Results.ok(list);
    }

    @Override
    public Result<R> create(ExtensionQueryRequest request, D dto) {
        T t = this.entityMapper.copyToPo(dto);
        T insert = this.requestRepository.save(t);
        return Results.ok(this.entityMapper.copyToVo(insert));
    }

    @Override
    public Result<R> update(ExtensionQueryRequest request, ID id, D dto) {
        Optional<T> optional = this.requestRepository.findById(id);
        if (optional.isEmpty()) {
            return Results.fail("can not find by id:" + id);
        }
        T t = this.entityMapper.copyToPo(dto);
        t = this.entityMapper.mergeNoNullVal(optional.get(), t);
        T save = this.requestRepository.save(t);
        return Results.ok(this.entityMapper.copyToVo(save));
    }

    @Override
    public Result<Void> delete(ExtensionQueryRequest request, Collection<ID> id) {
        this.requestRepository.deleteAllById(id);
        return Results.ok();
    }

    @Override
    public Result<Void> delete(ExtensionQueryRequest extensionQueryRequest, ID id) {
        return this.delete(extensionQueryRequest, Collections.singletonList(id));
    }
}